/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Reviewed:
	Copyright (c) Microsoft Corporation

	Mac standard dialogs.

***************************************************************************/
#include "frame.h"
ASSERTNAME


//REVIEW shonk: implement combo items.

/***************************************************************************
	Read the dialog resource and construct the GGDIT.
***************************************************************************/
bool DLG::_FInit(void)
{
	HN hn;
	short ridDitl;
	long sit, sitLim;
	long idit;
	byte bState;
	byte *pbDitl;
	byte bType;
	long cbEntry;
	DIT dit;
	bool fAddDit;
	bool fRet = fFalse;

	if ((hn = (HN)GetResource('DLOG', (short)_rid)) == hNil)
		{
		PushErc(ercDlgCantFind);
		return fFalse;
		}

	//get the rid of the DITL resource
	ridDitl = (*(short **)hn)[9];
	if ((hn = GetResource('DITL', ridDitl)) == hNil)
		{
		PushErc(ercDlgCantFind);
		return fFalse;
		}

	//lock the DITL so it doesn't move and so it isn't purged while
	//we're looking at it.
	bState = HGetState(hn);
	HLock(hn);
	pbDitl = *(byte **)hn;


	/***********************************************************************
	This info comes from New Inside Macintosh - Toolbox Essentials,
	pages 6-152 to 6-156.

	A DITL is a group of items preceeded by a short containing the number
	of items - 1.  Each item contains:

		4 bytes of reserved info
		8 bytes for a bounding rectangle
		1 byte for the item type and enable flag

	The remaining data in the item depends on the type of item.  For
	Buttons(4), checkboxes(5), radio buttons(6), static text(8) and
	editable text items(16), the item data continues with:

		1 - 256 bytes (variable) - an st containing the name
		0 or 1 byte alignment (to a short word)

	For compiled controls(7), icons(32) and pictures(64):

		1 byte reserved
		2 bytes resource number

	For application defined items(0):

		1 byte reserved

	For Help items(1):

		1 byte size - 4 or 6 (so we don't have to use the help item type)
		2 byte help item type
		2 byte resource number
		0 or 2 bytes for an item number (2 bytes if help item type is 8,
			otherwise 0 bytes)

	For other values of the item type, we assert and bag out.
	***********************************************************************/

	//get the lim of system items (they start at 1) and grow the GGDIT
	//to a reasonable size
	sitLim = (*(short *)pbDitl) + 2;
	pbDitl += size(short);
	if (!FEnsureSpace(sitLim, size(long), fgrpNil))
		goto LFail;

	//look at the items in the DITL
	for (idit = 0, sit = 1; sit < sitLim; sit++)
		{
		//skip the reserved bytes and rectangle
		pbDitl += 12;

		//the high bit is an enble bit, so mask it off
		bType = *pbDitl & 0x7F;
		pbDitl++;

		fAddDit = fFalse;
		switch (bType)
			{
		default:
			Bug("unkown item in DITL resource");
			goto LFail;

		case btnCtrl + ctrlItem: //button
			dit.ditk = ditkButton;
			cbEntry = 0;
			fAddDit = fTrue;
			goto LSkipSt;

		case chkCtrl + ctrlItem: //check box
			dit.ditk = ditkCheckBox;
			cbEntry = size(long);
			fAddDit = fTrue;
			goto LSkipSt;

		case radCtrl + ctrlItem: //radio button
			//if the last item added was a radio group, add this button to
			//the group, otherwise create a new radio group
			if (idit > 0)
				{
				GetDit(idit - 1, &dit);
				if (dit.ditk == ditkRadioGroup && dit.sitLim == sit)
					{
					dit.sitLim++;
					PutDit(idit - 1, &dit);
					goto LSkipSt;
					}
				}
			dit.ditk = ditkRadioGroup;
			cbEntry = size(long);
			fAddDit = fTrue;
			goto LSkipSt;

		case statText: //static text item
			goto LSkipSt;

		case editText: //edit item
			dit.ditk = ditkEditText;
			cbEntry = kcbMaxStz - kcchMaxStz;
			fAddDit = fTrue;
LSkipSt:
			pbDitl += CbSt((achar *)pbDitl);
			if ((long)pbDitl & 1)
				pbDitl++;
			break;

		case 7: //control
		case iconItem: //icon
		case picItem: //picture
			pbDitl += 3;
			break;

		case userItem: //app defined
			pbDitl += 1;
			break;

		case 1: //help item
			pbDitl += CbSt((achar *)pbDitl);
			break;
			}

		if (fAddDit)
			{
			Assert(cbEntry >= 0, 0);
			dit.sitMin = sit;
			dit.sitLim = sit + 1;
			if (!FInsert(idit, cbEntry, pvNil, &dit))
				goto LFail;

			//zero the extra data
			if (cbEntry > 0)
				ClearPb(QvGet(idit), cbEntry);
			idit++;
			}
		}

	FEnsureSpace(0, 0, fgrpShrink);
	fRet = fTrue;

LFail:
	HSetState(hn, bState);
	if (!fRet)
		PushErc(ercDlgOom);
	return fRet;
}


/***************************************************************************
	Actually put up the dialog and don't return until it comes down.
	Returns the idit that dismissed the dialog.  Returns ivNil on failure.
***************************************************************************/
long DLG::IditDo(long iditFocus)
{
	HDLG hdlg;
	DIT dit;
	short swHit;
	long idit = ivNil;

	if ((hdlg = GetNewDialog((short)_rid, pvNil, (PPRT)-1L)) == pvNil)
		{
		PushErc(ercDlgOom);
		goto LDone;
		}

	if (pvNil == (_pgob = NewObj GOB(khidDialog)))
		{
		PushErc(ercDlgOom);
		goto LDone;
		}

	if (!_pgob->FAttachHwnd((HWND)hdlg))
		{
		PushErc(ercDlgOom);
		goto LDone;
		}

	SetValues(0, IvMac());
	ShowWindow(hdlg);
	if (ivNil != iditFocus)
		SelectDit(iditFocus);
	for (;;)
		{
		ModalDialog(pvNil, &swHit);
		if ((idit = IditFromSit((long)swHit)) == ivNil)
			continue;

		GetDit(idit, &dit);
		switch (dit.ditk)
			{
		default:
			continue;

		case ditkButton:
			break;

		case ditkCheckBox:
			//invert it
			_InvertCheckBox(idit);
			break;

		case ditkRadioGroup:
			//set the value to swHit - dit.sitMin
			_SetRadioGroup(idit, (long)swHit - dit.sitMin);
			break;
			}

		if (_FDitChange(&idit))
			break;
		}

LDone:
	if (hdlg != hNil)
		{
		if (_pgob != pvNil)
			{
			if (idit != ivNil && !FGetValues(0, IvMac()))
				{
				PushErc(ercDlgCantGetArgs);
				idit = ivNil;
				}
			_pgob->FAttachHwnd(hNil);
			_pgob->Release();
			_pgob = pvNil;
			}
		DisposeDialog(hdlg);
		}

	return idit;
}


/***************************************************************************
	Get the value of a radio group.
***************************************************************************/
long DLG::_LwGetRadioGroup(long idit)
{
	HDLG hdlg;
	DIT dit;
	short sitk;
	HCTL hctl;
	RCS rcs;
	long sit;

	GetDit(idit, &dit);
	hdlg = (HDLG)_pgob->Hwnd();
	Assert(hdlg != hNil, "no dialog!");
	Assert(dit.ditk == ditkRadioGroup, "not a radio group!");

	for (sit = dit.sitMin; sit < dit.sitLim; sit++)
		{
		GetDialogItem(hdlg, (short)sit, &sitk, (HN *)&hctl, &rcs);
		Assert(sitk == (radCtrl + ctrlItem), "not a radio button!");
		if (GetCtlValue(hctl))
			return sit - dit.sitMin;
		}
	Bug("no radio button set");
	return dit.sitLim - dit.sitMin;
}


/***************************************************************************
	Change a radio group value.
***************************************************************************/
void DLG::_SetRadioGroup(long idit, long lw)
{
	HDLG hdlg;
	DIT dit;
	short sitk;
	HCTL hctl;
	RCS rcs;
	long sit;
	short swT;

	GetDit(idit, &dit);
	hdlg = (HDLG)_pgob->Hwnd();
	Assert(hdlg != hNil, "no dialog!");
	Assert(dit.ditk == ditkRadioGroup, "not a radio group!");
	AssertIn(lw, 0, dit.sitLim - dit.sitMin);

	GNV gnv(_pgob);
	gnv.Set();
	for (sit = dit.sitMin; sit < dit.sitLim; sit++)
		{
		GetDialogItem(hdlg, (short)sit, &sitk, (HN *)&hctl, &rcs);
		Assert(sitk == (radCtrl + ctrlItem), "not a radio button!");
		swT = GetCtlValue(hctl);

		//set the value
		if (swT)
			{
			if (sit - dit.sitMin != lw)
				SetCtlValue(hctl, 0);
			}
		else
			{
			if (sit - dit.sitMin == lw)
				SetCtlValue(hctl, 1);
			}
		}
	gnv.Restore();
}


/***************************************************************************
	Returns the current value of a check box.
***************************************************************************/
bool DLG::_FGetCheckBox(long idit)
{
	HDLG hdlg;
	DIT dit;
	short sitk;
	HCTL hctl;
	RCS rcs;

	GetDit(idit, &dit);
	hdlg = (HDLG)_pgob->Hwnd();
	Assert(hdlg != hNil, "no dialog!");
	Assert(dit.ditk == ditkCheckBox, "not a check box!");
	Assert(dit.sitLim == dit.sitMin + 1, "wrong lim on check box");

	GetDialogItem(hdlg, (short)dit.sitMin, &sitk, (HN *)&hctl, &rcs);
	Assert(sitk == (chkCtrl + ctrlItem), "not a check box!");
	return FPure(GetCtlValue(hctl));
}


/***************************************************************************
	Invert the value of a check box.
***************************************************************************/
void DLG::_InvertCheckBox(long idit)
{
	_SetCheckBox(idit, !_FGetCheckBox(idit));
}


/***************************************************************************
	Set the value of a check box.
***************************************************************************/
void DLG::_SetCheckBox(long idit, bool fOn)
{
	HDLG hdlg;
	DIT dit;
	short sitk;
	HCTL hctl;
	RCS rcs;
	short swT;

	GetDit(idit, &dit);
	hdlg = (HDLG)_pgob->Hwnd();
	Assert(hdlg != hNil, "no dialog!");
	Assert(dit.ditk == ditkCheckBox, "not a check box!");
	Assert(dit.sitLim == dit.sitMin + 1, "wrong lim on check box");

	GNV gnv(_pgob);
	gnv.Set();
	GetDialogItem(hdlg, (short)dit.sitMin, &sitk, (HN *)&hctl, &rcs);
	Assert(sitk == (chkCtrl + ctrlItem), "not a check box!");
	swT = GetCtlValue(hctl);
	if (FPure(swT) != FPure(fOn))
		SetCtlValue(hctl, FPure(fOn));
	gnv.Restore();
}


/***************************************************************************
	Get the text from an edit control.
***************************************************************************/
void DLG::_GetEditText(long idit, PSTZ pstz)
{
	HDLG hdlg;
	DIT dit;
	short sitk;
	HN hn;
	RCS rcs;

	GetDit(idit, &dit);
	hdlg = (HDLG)_pgob->Hwnd();
	Assert(hdlg != hNil, "no dialog!");
	Assert(dit.ditk == ditkEditText, "not edit item!");
	Assert(dit.sitLim == dit.sitMin + 1, "wrong lim on edit item");

	GetDialogItem(hdlg, (short)dit.sitMin, &sitk, &hn, &rcs);
	AssertVar(sitk == editText, "not an edit item!", &sitk);
	GetDialogItemText(hn, (byte *)pstz);
	StToStz(pstz);
}


/***************************************************************************
	Set the text in an edit control.
***************************************************************************/
void DLG::_SetEditText(long idit, PSTZ pstz)
{
	HDLG hdlg;
	DIT dit;
	short sitk;
	HN hn;
	RCS rcs;

	GetDit(idit, &dit);
	hdlg = (HDLG)_pgob->Hwnd();
	Assert(hdlg != hNil, "no dialog!");
	Assert(dit.ditk == ditkEditText, "not edit item!");
	Assert(dit.sitLim == dit.sitMin + 1, "wrong lim on edit item");

	GNV gnv(_pgob);
	gnv.Set();
	GetDialogItem(hdlg, (short)dit.sitMin, &sitk, &hn, &rcs);
	AssertVar(sitk == editText, "not an edit item!", &sitk);
	SetDialogItemText(hn, (byte *)pstz);
	gnv.Restore();
}


/***************************************************************************
	Make the given item the "focused" item and select its contents.  The
	item should be a text item.
***************************************************************************/
void DLG::SelectDit(long idit)
{
	HDLG hdlg;
	DIT dit;

	if (hNil == (hdlg = (HDLG)_pgob->Hwnd()))
		goto LBug;

	GetDit(idit, &dit);
	if (dit.ditk != ditkEditText)
		{
LBug:
		Bug("bad call to DLG::SelectDit");
		return;
		}
	Assert(dit.sitLim == dit.sitMin + 1, "wrong lim on edit item");
	SelIText(hdlg, (short)dit.sitMin, 0, kswMax);
}

